﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Globalization;
using System.ComponentModel;
using System.Threading;
using NUnit.Framework;
using System.Windows.Forms;


namespace Síť {
    public enum Connection_Errs {
        /// <summary>
        /// Connected OK.
        /// </summary>
        Connected,
        /// <summary>
        /// IP address not entered.
        /// </summary>
        WrongIP,
        /// <summary>
        /// Wrong port number.
        /// </summary>
        WrongPort,
        /// <summary>
        /// Error during connecting.
        /// </summary>
        CannotConnect,
        /// <summary>
        /// Stream is not readable or writable.
        /// </summary>
        WrongStream
    }

    /// <summary>
    /// Class for managing communication over TCP/IP protocol.
    /// </summary>
    public class Communicator {
        private delegate void WorkerEventHandler(AsyncOperation asyncOp);
        private SendOrPostCallback onMessageArrivedDelegate;

        public delegate void dMessageArrived(object source, MessageArrivedEventArgs e);
        public delegate void dDisconnected(object source, EventArgs e);
        public event dMessageArrived MessageArrived;
        public event dDisconnected Disconnected;
        public BackgroundWorker bwDisconnected = new BackgroundWorker();

        private TcpClient _tcpClient;
        private TcpListener _tcpListener;
        private IPEndPoint _EndPoint;
        private int _port = -1;
        private string _IPAddress = null;
        private NetworkStream _stream;

        private Thread tReader;

        /// <summary>
        /// Makes class ready for creating new connection.
        /// </summary>
        public Communicator() {
            //MessageBox.Show("Komunikátor Creator: " + Thread.CurrentThread.Name);
            bwDisconnected.DoWork += new DoWorkEventHandler(OnDisconnected);
            //bwMessageArrived.DoWork += new DoWorkEventHandler(OnMessageArrived);
            onMessageArrivedDelegate = new SendOrPostCallback(MessageArrivedCosi);
        }

        // This method is invoked via the AsyncOperation object,
        // so it is guaranteed to be executed on the correct thread.
        private void MessageArrivedCosi(object state) {
            MessageArrivedEventArgs e = state as MessageArrivedEventArgs;

            OnMessageArrived(this, e);
        }

        /// <summary>
        /// Raises event MessageArrived.
        /// </summary>
        /// <param name="e">Message content.</param>
        public void OnMessageArrived(object source, MessageArrivedEventArgs e) {
            if(MessageArrived != null)
                MessageArrived(this, e);
        }

        /// <summary>
        /// Raises event Disconnected.
        /// </summary>
        public void OnDisconnected(object source, DoWorkEventArgs e) {
            if(Disconnected != null)
                Disconnected(this, new EventArgs());
        }

        /// <summary>
        /// Create connection. IP address and port number must be already set.
        /// </summary>
        /// <returns>Information how successful connecting was.</returns>
        public Connection_Errs Connect() {
            return Connect(_IPAddress, _port);
        }

        /// <summary>
        /// Create connection.
        /// </summary>
        /// <param name="IPAddress">IP address to connect to in format "nnn.nnn.nnn.nnn".</param>
        /// <param name="port">Port number.</param>
        /// <returns>Information how successful connecting was.</returns>
        public Connection_Errs Connect(string IPAddress, int port) {
            _IPAddress = IPAddress;
            _port = port;

            _tcpClient = new TcpClient();

            if(IPAddress == null)
                return Connection_Errs.WrongIP;

            // port must be in the allowed range
            if(port < IPEndPoint.MinPort || port > IPEndPoint.MaxPort)
                return Connection_Errs.WrongPort;

            try {
                _tcpClient.Connect(IPAddress, port);
            }
            catch(SocketException socketEx) {
                return Connection_Errs.CannotConnect;
            }
            catch(ObjectDisposedException) {
                return Connection_Errs.CannotConnect;
            }

            if(!_tcpClient.Connected)
                return Connection_Errs.CannotConnect;

            return Communicator_Run();
        }

        /// <summary>
        /// Cancels connection.
        /// </summary>
        public void Disconnect() {

            if(_stream != null)
                _stream.Close();

            if(_tcpListener != null) {
                _tcpListener.Stop();
                _tcpListener = null;
            }

        }

        /// <summary>
        /// Creates stream for reading and writing to network.
        /// </summary>
        /// <returns>Information how successful stream creation was.</returns>
        private Connection_Errs Communicator_Run() {

            _stream = _tcpClient.GetStream();

            if(_stream.CanRead && _stream.CanWrite) {
                // starting threads
                
                // Task IDs are Guids.
                Guid taskId = Guid.NewGuid();
                // Start the asynchronous task.
                this.ReaderAsync(taskId);

            }
            else
                return Connection_Errs.WrongStream;

            return Connection_Errs.Connected;
        }

        // This method starts an asynchronous calculation. 
        // First, it checks the supplied task ID for uniqueness.
        // If taskId is unique, it creates a new WorkerEventHandler 
        // and calls its BeginInvoke method to start the calculation.
        public virtual void ReaderAsync(object taskId) {
            // Create an AsyncOperation for taskId.
            AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(taskId);

            // Start the asynchronous operation.
            WorkerEventHandler workerDelegate = new WorkerEventHandler(Reader);
            workerDelegate.BeginInvoke(asyncOp, null, null);
        }

        /// <summary>
        /// Stops listening.
        /// </summary>
        public void CancelListening() {
            if(_tcpListener != null)
                _tcpListener.Stop();
        }


        /// <summary>
        /// Starts listening for connection on port set in properties.
        /// </summary>
        /// <returns>Information how successful listening was.</returns>
        public Connection_Errs Listen() {
            return Listen(_port);
        }

        /// <summary>
        /// Starts listening for connection on port.
        /// </summary>
        /// <param name="port">Port number to listen on.</param>
        /// <returns>Information how successful listening was.</returns>
        public Connection_Errs Listen(int port) {
            if(port < IPEndPoint.MinPort || port > IPEndPoint.MaxPort)
                return Connection_Errs.WrongPort;

            // TODO: Opravit vytvareni nove instance TcpListener - obsolete.
            _tcpListener = new TcpListener(port);

            _tcpListener.Start();

            try {
                _tcpClient = _tcpListener.AcceptTcpClient();
            }
            catch(SocketException ex) {
                return Connection_Errs.CannotConnect;
            }

            _tcpListener.Stop();

            if(!_tcpClient.Connected) {
                return Connection_Errs.CannotConnect;
            }

            _IPAddress = ((IPEndPoint)_tcpClient.Client.RemoteEndPoint).Address.ToString();
            _port = ((IPEndPoint)_tcpClient.Client.RemoteEndPoint).Port;

            return Communicator_Run();

        }

        private void Reader(AsyncOperation asyncOp) {
            int znak;
            string Message;

            while(_tcpClient.Connected) {

                byte[] tmp = new byte[0];
                znak = 0;
                Message = null;

                // separator is Asc(0)
                try {
                    while((znak = _stream.ReadByte()) > 0) {
                        Array.Resize<byte>(ref tmp, tmp.Length + 1);
                        tmp[tmp.GetUpperBound(0)] = (byte)znak;
                        Array.Resize<byte>(ref tmp, tmp.Length + 1);
                        tmp[tmp.GetUpperBound(0)] = (byte)_stream.ReadByte();
                    }
                    if(znak == -1) {
                        bwDisconnected.RunWorkerAsync();
                        return;
                    }

                }
                catch(System.IO.IOException) {
                    bwDisconnected.RunWorkerAsync();
                    return;
                }
                Message = Encoding.Unicode.GetString(tmp);

                MessageArrivedEventArgs e = new MessageArrivedEventArgs(Message);

                asyncOp.Post(this.onMessageArrivedDelegate, e);

            }

        }

        public bool Write(string Msg) {
            byte[] tmp;
            tmp = Encoding.Unicode.GetBytes(Msg);
            try {
                _stream.Write(tmp, 0, tmp.Length);
                _stream.WriteByte(0);
            }
            catch(Exception ex) {
                return false;
            }
            return true;
        }

        ~Communicator() {
            this.Disconnect();
        }

        #region "Properties"


        /// <summary>
        /// Port number.
        /// </summary>
        public int Port {
            get {
                return _port;
            }
            set {
                _port = value;
                if(_IPAddress != null && _port > -1)
                    _EndPoint = new IPEndPoint(IPAddress.Parse(_IPAddress), _port);

            }
        }

        /// <summary>
        /// IP address of remote computer.
        /// </summary>
        public string IPAdress {
            get {
                return _IPAddress;
            }
            set {
                _IPAddress = value;
            }

        }

        public Boolean Connected {
            get {
                if(_tcpClient == null)
                    return false;
                else
                    return _tcpClient.Client.Connected;
            }
        }

        #endregion

    }

    public class MessageArrivedEventArgs: EventArgs {
        private string _msg;

        public MessageArrivedEventArgs(string msg) {
            _msg = msg;
        }

        public string Message {
            get {
                return _msg;
            }
        }
    }

}
